﻿using UnityEngine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;

namespace XLua
{
    [LuaCallCSharp]
    public class LuaManager : MonoBehaviour
    {
        private static LuaManager m_instance;
        public static LuaManager Instance
        {
            get
            {
                if (m_instance == null)
                {
                    GameObject go = new GameObject();
                    m_instance = go.AddComponent<LuaManager>();
                }
                return m_instance;
            }
        }
        public static bool IsInited
        {
            get
            {
                return m_instance != null;
            }
        }

        private LuaEnv _lua;

        public LuaEnv Lua
        {
            get
            {
                return _lua;
            }
        }

        private List<string> _searchPath = new List<string>();
        private Dictionary<string, LuaTable> _tables = new Dictionary<string, LuaTable>();
        private Dictionary<string, AssetBundle> _luaBundle = new Dictionary<string, AssetBundle>();

        private List<LuaFunction> _updateList = new List<LuaFunction>();
        private List<LuaFunction> _fixedUpdateList = new List<LuaFunction>();
        private List<LuaFunction> _lateUpdateList = new List<LuaFunction>();

        // Use this for initialization
        void Awake()
        {
            if (m_instance != null)
            {
                Destroy(gameObject);
                return;
            }
            AddSearchPath();

            _lua = new LuaEnv();
            _lua.AddBuildin("rapidjson", LuaDLL.Lua.LoadRapidJson);
            // _lua.AddBuildin("lpeg", LuaDLL.Lua.LoadLpeg);
            _lua.AddBuildin("pb", LuaDLL.Lua.LoadLuaProfobuf);
            // _lua.AddBuildin("ffi", LuaDLL.Lua.LoadFFI);
            _lua.AddLoader(CustomLoaderMethod);

            DontDestroyOnLoad(gameObject);
            m_instance = this;
            name = "[LuaManager]";

            InitStart();

            UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnLevelLoaded;
        }

        #region  
        public void AddUpdate(LuaFunction lb)
        {
            if (lb != null && !_updateList.Contains(lb))
            {
                _updateList.Add(lb);
            }
        }
        public void AddFixedUpdate(LuaFunction lb)
        {
            if (lb != null && !_fixedUpdateList.Contains(lb))
            {
                _fixedUpdateList.Add(lb);
            }
        }
        public void AddLateUpdate(LuaFunction lb)
        {
            if (lb != null && !_lateUpdateList.Contains(lb))
            {
                _lateUpdateList.Add(lb);
            }
        }
        public void RemoveUpdate(LuaFunction lb)
        {
            if (lb != null && _updateList.Contains(lb))
            {
                _updateList.Remove(lb);
                lb.Dispose();
            }
        }
        public void RemoveFixedUpdate(LuaFunction lb)
        {
            if (lb != null && _fixedUpdateList.Contains(lb))
            {
                _fixedUpdateList.Remove(lb);
                lb.Dispose();
            }
        }
        public void RemoveLateUpdate(LuaFunction lb)
        {
            if (lb != null && _lateUpdateList.Contains(lb))
            {
                _lateUpdateList.Remove(lb);
                lb.Dispose();
            }
        }

        private void Update()
        {
            for (int i = _updateList.Count - 1; i >= 0; i--)
            {
                var fun = _updateList[i];
                if (fun != null)
                {
                    fun.Call();
                }
                else
                {
                    _updateList.RemoveAt(i);
                }
            }
        }
        private void FixedUpdate()
        {
            for (int i = _fixedUpdateList.Count - 1; i >= 0; i--)
            {
                var fun = _fixedUpdateList[i];
                if (fun != null)
                {
                    fun.Call();
                }
                else
                {
                    _fixedUpdateList.RemoveAt(i);
                }
            }
        }
        private void LateUpdate()
        {
            for (int i = _lateUpdateList.Count - 1; i >= 0; i--)
            {
                var fun = _lateUpdateList[i];
                if (fun != null)
                {
                    fun.Call();
                }
                else
                {
                    _lateUpdateList.RemoveAt(i);
                }
            }
        }
        #endregion



        void AddSearchPath()
        {
            if (AppConst.LuaBundleMode)
            {
                _searchPath.Add(Application.persistentDataPath + "/lua/");
                _searchPath.Add(Application.streamingAssetsPath + "/lua/");
            }
            else
            {
                _searchPath.Add(Application.dataPath + "/Xlua/Lua/");
                _searchPath.Add(Application.dataPath + "/Lua/");
            }
        }


        byte[] byte_key = { 0x38, 0x2A, 0x28, 0x61, 0x79, 0x74, 0x62, 0x34 };
        byte[] byte_iv = { 0x3F, 0x2D, 0x2F, 0x79, 0x54, 0xA6, 0xE2, 0x32 };
        byte[] Decryp(byte[] encryptedData)
        {
            using (DESCryptoServiceProvider des = new DESCryptoServiceProvider())
            {
                des.Key = byte_key;
                des.IV = byte_iv;

                // 创建一个内存流来存储解密后的数据
                using (var memoryStream = new MemoryStream())
                {
                    // 创建一个解密流来执行解密操作
                    using (var cryptoStream = new CryptoStream(memoryStream, des.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        // 将加密数据写入解密流
                        cryptoStream.Write(encryptedData, 0, encryptedData.Length);
                        cryptoStream.FlushFinalBlock();
                        return memoryStream.ToArray();
                    }
                }
            }
        }

        byte[] CustomLoaderMethod(ref string file)
        {
            if (AppConst.LuaBundleMode)
            {
                string luaName = $"Assets/{AppConst.LuaTempDir}/{file}.lua.bytes";
                foreach (var root in _searchPath)
                {
                    AssetBundle bundle = null;

                    string key = "";
                    if (AppConst.LuaBundleByFolder)
                    {
                        key = "lua_" + Path.GetDirectoryName(file.ToLower() + ".lua").Replace("/", "_") + AppConst.ExtName;
                    }
                    else
                    {
                        key = "lua" + AppConst.ExtName;
                    }

                    if (_luaBundle.ContainsKey(key))
                    {
                        bundle = _luaBundle[key];
                    }
                    string path = root + key;
                    if (bundle == null)
                    {
                        var bytes = LoadLocalBytes(path);
                        if (bytes != null)
                        {
                            bytes = Decryp(bytes);
                            bundle = AssetBundle.LoadFromMemory(bytes);
                            _luaBundle[key] = bundle;
                        }
                    }

                    if (bundle != null)
                    {
                        var ta = bundle.LoadAsset<TextAsset>(luaName);
                        var bytes = ta.bytes;
                        Resources.UnloadAsset(ta);
                        return bytes;
                    }
                }
            }
            else
            {
                foreach (var root in _searchPath)
                {
                    string path = root + file + ".lua";
                    var bytes = LoadLocalBytes(path);
                    if (bytes != null) return bytes;
                }
            }
            return null;
        }
        //加载本地文件
        byte[] LoadLocalBytes(string path)
        {
#if UNITY_EDITOR
            if (File.Exists(path))
            {
                return File.ReadAllBytes(path);
            }
            return null;
#endif

            if (File.Exists(path))
            {
                return File.ReadAllBytes(path);
            }
            else
            {
                if (Application.platform == RuntimePlatform.IPhonePlayer) path = "file://" + path;
                var webRequest = UnityEngine.Networking.UnityWebRequest.Get(path);
                try
                {
                    webRequest.SendWebRequest();
                    while (true)
                    {
                        if (webRequest.isDone || webRequest.isNetworkError || webRequest.isHttpError) break;
                    }
                    if (!webRequest.isNetworkError && !webRequest.isHttpError)
                    {
                        return webRequest.downloadHandler.data;
                    }
                }
                finally
                {
                    webRequest.Dispose();
                }
            }
            return null;
        }

        string SearchFile(string file)
        {
            foreach (var root in _searchPath)
            {
                string path = root + file + ".lua";
                if (File.Exists(path)) return path;
            }
            return null;
        }


        void OnLevelLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
        {
            if (mode == UnityEngine.SceneManagement.LoadSceneMode.Single && _lua != null)
            {
                Action func = _lua.Global.Get<Action>("OnLevelWasLoaded");
                func?.Invoke();
            }
        }

        void InitStart()
        {
            _lua.DoString("require 'xlua/functions'");
            _lua.DoString("require 'xlua/utf8'");

            _lua.DoString("require 'Comm/Main'");
            Action func = _lua.Global.Get<Action>("Main");
            func?.Invoke();
        }


        public void DoLuaString(string data, string chunkName)
        {
            _lua.DoString(data, chunkName);
        }

        public LuaTable DoTable(string file)
        {
            if (_tables.ContainsKey(file) && _tables[file] != null)
            {
                return _tables[file];
            }
            byte[] bytes = CustomLoaderMethod(ref file);
            if (bytes != null)
            {
                var tb = _lua.DoString(bytes, Path.GetFileName(file));
                if (tb != null && tb[0] is LuaTable)
                {
                    _tables[file] = tb[0] as LuaTable;
                    return tb[0] as LuaTable;
                }
            }
            return null;
        }

        void OnDestroy()
        {
            if (_lua != null)
            {
                _lua.Dispose();
                _lua = null;
            }
            _searchPath.Clear();
            _tables.Clear();
            foreach (var v in _luaBundle.Values)
            {
                if (v != null) v.Unload(true);
            }
            _luaBundle.Clear();
            _updateList.Clear();
            _fixedUpdateList.Clear();
            _lateUpdateList.Clear();
        }
    }
}